using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
using Autofac;
using Microsoft.Practices.ServiceLocation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NQueries.Data;
using NQueries.Utils;
using TestModel;
using TestSupport;

namespace SystemTest
{
    [TestClass]
    public class SystemTest
    {
        [ClassInitialize]
        public static void ClassInitialize(TestContext testContext) { DbTools.DropTestDatabase(); }

        [TestInitialize]
        public void TestInitialize()
        {
            IContainer container = AutofacContainerBuilder.Build();
            ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));

            DbTools.RestoreTestDatabase();
        }

        [TestCleanup]
        public void TestCleanup()
        {
            Assert.IsNull(Transaction.Current);
            DbTools.DropTestDatabase();
        }

        static Customer CreateNewCustomer()
        {
            return new Customer
                   {
                       rowguid = Guid.NewGuid(),
                       FirstName = "Michael",
                       LastName = "Sander",
                       PasswordHash = string.Empty,
                       PasswordSalt = string.Empty,
                       ModifiedDate = DateTime.UtcNow
                   };
        }

        [TestMethod]
        public void AddOnSubmit()
        {
            Customer customer = CreateNewCustomer();
            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("AddOnSubmit.Create"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                customerRepository.AddOnSubmit(customer);
                unitOfWork.Commit();
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == customer.CustomerID);
                Assert.AreEqual(1, customers.Count());
                Assert.AreEqual(customer.CustomerID, customers.Single().CustomerID);
            }
        }

        [TestMethod]
        public void RemoveOnSubmit()
        {
            Customer newCustomer = CreateNewCustomer();

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "RemoveOnSubmit.Create"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                customerRepository.AddOnSubmit(newCustomer);
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "RemoveOnSubmit.Delete"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                customerRepository.Attach(newCustomer);
                customerRepository.RemoveOnSubmit(newCustomer);
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "RemoveOnSubmit.Check"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                Assert.AreEqual(0, customers.Count());
            }
        }

        [TestMethod]
        public void Error()
        {
            Customer newCustomer = CreateNewCustomer();

            TestTools.AssertThrows<NotImplementedException>(
                () =>
                {
                    using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "Error.Create"))
                    {
                        unitOfWork.GetRepository<Customer>().AddOnSubmit(newCustomer);
                        throw new NotImplementedException();
                    }
                });

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "Error.Check"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                Assert.AreEqual(0, customers.Count());
            }
        }

        [TestMethod]
        public void NestedError()
        {
            Customer newCustomer = CreateNewCustomer();

            TestTools.AssertThrows<NotImplementedException>(
                () =>
                {
                    using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "NestedError.Create"))
                    {
                        unitOfWork.GetRepository<Customer>().AddOnSubmit(newCustomer);
                        using (UnitOfWork.Begin(TransactionScopeOption.Required, "NestedError.Create2"))
                        {
                            throw new NotImplementedException();
                        }
                    }
                });

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "NestedError.Check"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                Assert.AreEqual(0, customers.Count());
            }
        }

        [TestMethod]
        public void MultipleThreadsMultipleTransactions()
        {
            Customer newCustomer = CreateNewCustomer();

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "MultipleThreadsMultipleTransactions.Create"))
            {
                Task task = new Task(
                    () =>
                    {
                        using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(TransactionScopeOption.Required, "MultipleThreadsMultipleTransactions.Create2"))
                        {
                            unitOfWork2.GetRepository<Customer>().AddOnSubmit(newCustomer);
                            unitOfWork2.Commit();
                        }
                    });
                task.Start();
                Task[] tasks = new[] { task };
                Task.WaitAll(tasks);
                unitOfWork.Commit();
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "MultipleThreadsMultipleTransactions.Check"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                Assert.AreEqual(1, customers.Count());
            }
        }

        [TestMethod]
        public void MultipleThreadsSingleTransactionRollback()
        {
            Customer newCustomer = CreateNewCustomer();

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "MultipleThreadsSingleTransactionRollback.Create"))
            {
                DependentTransaction dependentTransaction = unitOfWork.GetDependentTransaction(DependentCloneOption.BlockCommitUntilComplete);

                using (Task task = new Task(
                    () =>
                    {
                        using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(dependentTransaction, "MultipleThreadsSingleTransactionRollback.Create2"))
                        {
                            unitOfWork2.GetRepository<Customer>().AddOnSubmit(newCustomer);
                            unitOfWork2.Commit();
                        }
                    }))
                {
                    task.Start();
                    Task[] tasks = new[] { task };
                    Task.WaitAll(tasks);
                }

                using (IUnitOfWork unitOfWork3 = UnitOfWork.Begin(TransactionScopeOption.Required, "MultipleThreadsSingleTransactionRollback.Check1"))
                {
                    IRepository<Customer> customerRepository = unitOfWork3.GetRepository<Customer>();
                    IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                    Assert.AreEqual(1, customers.Count());
                    unitOfWork3.Commit();
                }
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("MultipleThreadsSingleTransactionRollback.Check2"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                Assert.AreEqual(0, customers.Count());
            }
        }

        [TestMethod]
        public void MultipleThreadsSingleTransaction()
        {
            Customer newCustomer = CreateNewCustomer();

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "MultipleThreadsSingleTransaction.Create"))
            {
                DependentTransaction dependentTransaction = unitOfWork.GetDependentTransaction(DependentCloneOption.RollbackIfNotComplete);
                using (Task task = new Task(
                    () =>
                    {
                        using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(dependentTransaction, "MultipleThreadsSingleTransaction.Create2"))
                        {
                            unitOfWork2.GetRepository<Customer>().AddOnSubmit(newCustomer);
                            unitOfWork2.Commit();
                        }
                    }))
                {
                    task.Start();
                    Task[] tasks = new[] { task };
                    Task.WaitAll(tasks);
                }
                unitOfWork.Commit();
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Suppress, "MultipleThreadsSingleTransaction.Check2"))
            {
                IRepository<Customer> customerRepository = unitOfWork.GetRepository<Customer>();
                IEnumerable<Customer> customers = customerRepository.Where(x => x.CustomerID == newCustomer.CustomerID);
                Assert.AreEqual(1, customers.Count());
            }
        }

        [TestMethod]
        public void Inheritance()
        {
            Employee employee = new Employee { Id = Guid.NewGuid(), EntryDate = DateTime.Now, Name = "Name" };

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required, "Inheritance.Create"))
            {
                IRepository<Employee> employeeRepository = unitOfWork.GetRepository<Employee>();
                employeeRepository.AddOnSubmit(employee);
                unitOfWork.Commit();
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
            {
                IRepository<Person> peopleRepository = unitOfWork.GetRepository<Person>();
                IEnumerable<Person> people = peopleRepository.Where(x => x.Id == employee.Id);
                Assert.AreEqual(1, people.Count());
            }
        }
    }
}